查看原文
其他

聊聊分布式日志系统的设计与实践

架构精进之路 架构精进之路
2024-08-31

hello,大家好,我是张张,「架构精进之路」公号作者。


1、什么是日志

日志是一种按照时间顺序存储记录的数据,它记录了什么时间发生了什么事情,提供精确的系统记录,根据日志信息可以定位到错误详情和根源。按照APM概念的定义,日志的特点是描述一些离散的(不连续的)事件。
由于日志本身固有的特性,记录从左向右开始顺序插入,也就意味着左边的记录相较于右边的记录“更老”, 也就是说我们可以不用依赖于系统时钟,这个特性对于分布式系统来说相当重要。

日志是按照错误级别分级的,常见的错误级别有 FATAL / WARNING / NOTICE / DEBUG / TRACE 5种类型。通常我们会在项目里面定义一个日志打印级别,高于这个级别的错误日志会数据落盘。



2、日志的价值


在大型网站系统架构里面,日志是其中的重要功能组成部分。它可以记录下系统所产生的所有行为,并按照某种规范表达出来。
日志是记录系统中各种问题信息的关键,也是一种常见的海量数据。日志平台为集团所有业务系统提供日志采集、消费、分析、存储、索引和查询的一站式日志服务。主要为了解决日志分散不方便查看、日志搜索操作复杂且效率低、业务异常无法及时发现等等问题。
我们可以使用日志系统所记录的信息为系统进行排错,优化性能。通过统计用户行为日志,帮助产品运营同学做业务决策。在安全领域,日志可以反应出很多的安全攻击行为,比如登录错误,异常访问等。日志能告诉你很多关于网络中所发生事件的信息,包括性能信息、故障检测和入侵检测。还可以为审计进行审计跟踪,日志的价值是显而易见的。


3、APM和可观测性


APM 是Application Performance Managment的缩写,即:“应用性能管理”。可以把它理解成一种对分布式架构进行观测分析优化的理念和方法论。监控系统(包括告警)作为SLA体系的一个重要组成部分,不仅在业务和系统中充当保镖发现问题、排查问题的作用。

整体来说,整个APM体系就是将大三类数据(logs、metrics、trace)应用到四大模块中(收集、加工、存储、展示),并在四个难点(程序异构,组件多样,链路完整,时效采样)上不断优化。

可观测性 是APM的一大特征,主要由以下三大支柱构成,分别是Logging(日志),Metrics(指标),以及Tracing(应用跟踪)。

  • Logging:自动埋点/手动埋点,展现的是应用运行而产生的事件或者程序在执行的过程中间产生的一些日志,可以详细解释系统的运行状态,但是存储和查询需要消耗大量的资源。

  • Metrics:服务、端点、实例的各项指标,是一种聚合数值,存储空间很小,可以观察系统的状态和趋势,对于问题定位缺乏细节展示,最节省存储资源。

  • Tracing:同一TraceId的调用序列,面向的是请求,可以轻松分析出请求中异常点,资源可能消耗较大,不过依据具体功能实现相对可控。

3.1 Metrics和Prometheus

Metrics:指标。

I think that the defining characteristic of metrics is that they are aggregatable: they are the atoms that compose into a single logical gauge, counter, or histogram over a span of time.

大致上可理解为一些可进行聚合计算的原子型数据。举些例子:cpu占用情况、系统内存占用、接口响应时间、接口响应QPS、服务gc次数、订单量等。这些都是根据时间序列存储的数据值,可以在一段时间内进行一些求和、求平均、百分位等聚合计算。指标在监控系统中不可或缺,我们都需要收集每种指标在时间线上的变化,并作同比、环比上的分析。metrics的存储形式为有时间戳标记的数据流,通常存储在TSDB(时间序列数据库)中。

Metrics侧重于各种报表数据的收集和展示,常用在大规模业务的可用性建设、性能优化、容量管理等场景,通过可视化仪表盘可高效地进行日常系统巡检、快速查看应用健康状况,可以精准感知可用性和性能问题,为产品的稳定运行保驾护航。

Prometheus 是一个开源的监控解决方案,它能够提供监控指标数据的采集、存储、查询以及监控告警等功能。作为云原生基金会(CNCF)的毕业项目,Prometheus 已经在云原生领域得到了大范围的应用,并逐渐成为了业界最流行的监控解决方案之一。

下图为Prometheus的工作流程,可以简单理解为:Prometheus server定期拉取目标实例的采集数据,时间序列存储,一方面通过配置报警规则,把触发的报警发送给接收方,一方面通过组件Grafana把数据以图形化形式展示给用户。

3.2 Logging和ELK

Logging:日志。

I think that the defining characteristic of logging is that it deals with discrete events.

日志是系统运行时发生的一个个事件的记录。Logging的典型特征就是它和孤立的事件(Event)强关联,一个事件的产生所以导致了一条日志的产生。举个例子就是一个网络请求是一个事件,它被云端接到后Nginx产生了一个访问log。大量的不同外部事件间基本是离散的,比如多个用户访问云端业务时产生的5个事件间没有必然的关系,所以在一个服务节点的角度上看这些事件产生的日志间也是离散的。

关于日志管理平台,相信很多同学听说过最多的就是ELK(elastic stack),ELK是三款软件的简称,分别是Elasticsearch、 Logstash、Kibana组成。在APM体系中,它可以实现关键字的分布式搜索和日志分析,能够快速定位到我们想要的日志,通过可视化平台的展示,能够从多个维度来对日志进行细化跟踪。


Elasticsearch基于java,是个开源分布式搜索引擎,它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。

Kibana基于nodejs,是一款开源的数据分析和可视化平台,它是Elastic Stack成员之一,设计用于和Elasticsearch协作。您可以使用Kibana对Elasticsearch索引中的数据进行搜索、查看、交互操作。您可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。

Logstash基于java,是一个开源的用于收集,分析和存储日志的工具,能够同时从多个来源采集数据,转换数据,然后将数据发送到最喜欢的存储库中(我们的存储库当然是ElasticSearch)。


下面是ELK的工作原理: 

ELK中的L理解成Logging Agent比较合适。Elasticsearch和Kibana是存储、检索和分析log的标准方案。在高负载的ELK平台迭代实践中,常常采用一些优化策略。比如:ElasticSearch 做冷热数据分离,历史索引数据关闭;Filebeat更加轻量,对资源消耗更少,替代Logstash作为数据收集引擎;增加消息队列做数据缓冲,通过解耦处理过程实现削峰平谷,帮助平台顶住突发的访问压力。

ELK的缺点也是明显的,部署这样一套日志分析系统,不论是存储还是分析所需要占用的机器成本是挺大的。业务日志是时时打印的,大规模的在线服务一天日志量可能达到TB级别,如果采用ELK平台,在保证关键日志信息入库的同时,有针对性的对所需日志文件进行采集和过滤是必不可少的。

3.3 Tracing、OpenTracing和Apache SkyWalking

.Tracing:链路。

I think that the single defining characteristic of tracing , then, is that it deals with information that is request-scoped.

链路可理解为某个最外层请求下的所有调用信息。在微服务中一般有多个调用信息,如从最外层的网关开始,A服务调用B服务,调用数据库、缓存等。在链路系统中,需要清楚展现某条调用链中从主调方到被调方内部所有的调用信息。这不仅有利于梳理接口及服务间调用的关系,还有助于排查慢请求产生的原因或异常发生的原因。

Tracing最早提出是来自Google的论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,它让Tracing流行起来。而Twitter基于这篇论文开发了Zipkin并开源了这个项目。再之后业界百花齐放,诞生了一大批开源和商业Tracing系统。

Tracing 以请求的维度,串联服务间的调用关系并记录调用耗时,即保留了必要的信息,又将分散的日志事件通过Span层串联, 帮助我们更好的理解系统的行为、辅助调试和排查性能问题。它的基本概念如下两点:

  1. Trace(调用链):OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图),可以简单理解成一次事务;

  2. Span(跨度):可以被翻译为跨度,可以被理解为一次方法调用,一个程序块的调用,或者一次RPC/数据库访问,只要是一个具有完整时间周期的程序访问,都可以被认为是一个Span。

对于一个组件来说,一次处理过程产生一个 Span,这个 Span 的生命周期是从接收到请求到返回响应这段过程,在单个Trace中,存在多个Span。

举个例子,比如一个请求用户订单信息的接口,流量分发到了应用层实例(Span A)来处理请求,应用层实例(Span A)需要请求订单中心服务实例(Span B)来获取订单数据,同时请求用户中心服务实例(Span C)来获取用户数据。基础服务B、C可能还有其他依赖服务链路,则如下图所示结构,Span间的因果关系如下:

        [Span A]  ←←←(the root span)            |     +------+------+     |             | [Span B]      [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)     |             | [Span D]      +---+-------+               |           |           [Span E]    [Span F] >>> [Span G] >>> [Span H]                                       ↑                                       ↑                                       ↑                         (Span G 在 Span F 后被调用, FollowsFrom)

OpenTracing是一个中立的(厂商无关、平台无关)分布式追踪的API 规范,提供统一接口,可方便开发者在自己的服务中集成一种或多种分布式追踪的实现。由于近年来各种链路监控产品层出不穷,当前市面上主流的工具既有像Datadog这样的一揽子商业监控方案,也有AWS X-Ray和Google Stackdriver Trace这样的云厂商产品,还有像Zipkin、Jaeger这样的开源产品。

云原生基金会(CNCF) 推出了OpenTracing标准,推进Tracing协议和工具的标准化,统一Trace数据结构和格式。OpenTracing通过提供平台无关、厂商无关的API,使得开发人员能够方便添加(或更换)追踪系统的实现。比如从Zipkin替换成Jaeger/Skywalking等后端。

在众多Tracing产品中,值得一提的是国人自研开源的产品Skywalking。它是一款优秀的APM工具,专为微服务、云原生架构和基于容器架构而设计,支持Java、.Net、NodeJs等探针方式接入项目,数据存储支持Mysql、Elasticsearch等。功能包括了分布式链路追踪,性能指标分析和服务依赖分析等。

3.4 Metrics,Logging和 Tracing 结合

指标、日志、链路在监控中是相辅相成的。现在再来看上图中,两两相交的部分:

  1. 通过指标和日志维度,我们可以做一些事件的聚合统计,例如,绘制流量趋势图,某应用每分钟的错误日志数

  2. 通过链路和日志系统,我们可以得到某个请求详细的请求信息,例如请求的入参、出参、链路中途方法打印出的日志信息;

  3. 通过指标和链路系统,我们可以查到请求调用信息,例如 SQL执行总时长、各依赖服务调用总次数;

可见,通过这三种类型数据相互作用,可以得到很多在某种类型数据中无法呈现的信息。例如下图是一个故障排查的示例,首先,我们从消息通知中发现告警,进入metrics指标面板,定位到有问题的数据图表,再通过指标系统查询到详细的数据,在logging日志系统查询到对应的错误,通过tracing链路追踪系统查看链路中的位置和问题(当然也可以先用链路追踪系统进行故障的定位,再查询详细日志),最后修复故障。这是一个典型的将三个系统串联起来应用的示例。



4、日志的应用

4.1 日志在数据库中的应用

在数据库领域中日志更多的是用于在系统crash的时候同步数据以及索引等,例如MySQL中的redo log,redo log是一种基于磁盘的数据结构,用于在系统挂掉的时候保证数据的正确性、完整性,也叫预写日志,例如在一个事物的执行过程中,首先会写redo log,然后才会应用实际的更改,这样当系统crash后恢复时就能够根据redo log进行重放从而恢复数据(在初始化的过程中,这个时候不会还没有客户端的连接)。

日志也可以用于数据库主从之间的同步,因为本质上,数据库所有的操作记录都已经写入到了日志中,我们只要将日志同步到slave,并在slave重放就能够实现主从同步,这里也可以实现很多其他需要的组件,我们可以通过订阅redo log 从而拿到数据库所有的变更,从而实现个性化的业务逻辑,例如审计、缓存同步等等。

4.2 日志在分布式系统中的应用


分布式系统服务本质上就是关于状态的变更,这里可以理解为状态机,两个独立的进程(不依赖于外部环境,例如系统时钟、外部接口等)给定一致的输入将会产生一致的输出并最终保持一致的状态,而日志由于其固有的顺序性并不依赖系统时钟,正好可以用来解决变更有序性的问题。

我们利用这个特性实现解决分布式系统中遇到的很多问题。例如RocketMQ中的备节点,主broker接收客户端的请求,并记录日志,然后实时同步到salve中,slave在本地重放,当master挂掉的时候,slave可以继续处理请求,例如拒绝写请求并继续处理读请求。日志中不仅仅可以记录数据,也可以直接记录操作,例如SQL语句。


日志是解决一致性问题的关键数据结构,日志就像是操作序列,每一条记录代表一条指令,例如应用广泛的Paxos、Raft协议,都是基于日志构建起来的一致性协议。



4.3 日志在Message Queue中的应用

日志可以很方便的用于处理数据之间的流入流出,每一个数据源都可以产生自己的日志,这里数据源可以来自各个方面,例如某个事件流(页面点击、缓存刷新提醒、数据库binlog变更),我们可以将日志集中存储到一个集群中,订阅者可以根据offset来读取日志的每条记录,根据每条记录中的数据、操作应用自己的变更。

这里的日志可以理解为消息队列,消息队列可以起到异步解耦、限流的作用。为什么说解耦呢?因为对于消费者、生产者来说,两个角色的职责都很清晰,就负责生产消息、消费消息,而不用关心下游、上游是谁,不管是来数据库的变更日志、某个事件也好,对于某一方来说我根本不需要关心,我只需要关注自己感兴趣的日志以及日志中的每条记录。

我们知道数据库的QPS是一定的,而上层应用一般可以横向扩容,这个时候如果到了双11这种请求突然的场景,数据库会吃不消,那么我们就可以引入消息队列,将每个队数据库的操作写到日志中,由另外一个应用专门负责消费这些日志记录并应用到数据库中,而且就算数据库挂了,当恢复的时候也可以从上次消息的位置继续处理(RocketMQ和Kafka都支持Exactly Once语义),这里即使生产者的速度异于消费者的速度也不会有影响。

日志在这里起到了缓冲的作用,它可以将所有的记录存储到日志中,并定时同步到slave节点,这样消息的积压能力能够得到很好的提升,因为写日志都是有master节点处理,读请求这里分为两种,一种是tail-read,就是说消费速度能够跟得上写入速度的,这种读可以直接走缓存,而另一种也就是落后于写入请求的消费者,这种可以从slave节点读取,这样通过IO隔离以及操作系统自带的一些文件策略,例如pagecache、缓存预读等,性能可以得到很大的提升。


结语

日志在分布式系统中扮演了很重要的角色,是理解分布式系统各个组件的关键,随着理解的深入,我们发现很多分布式中间件都是基于日志进行构建的,例如Zookeeper、HDFS、Kafka、RocketMQ、Google Spanner等等,甚至于数据库,例如Redis、MySQL等等,其master-slave都是基于日志同步的方式。
依赖共享的日志系统,我们可以实现很多系统: 节点间数据同步、并发更新数据顺序问题(一致性问题)、持久性(系统crash时能够通过其他节点继续提供服务)、分布式锁服务等等,相信慢慢的通过实践、以及大量的论文阅读之后,一定会有更深层次的理解。
希望本文的内容对大家的实践有所帮助。


·END·

相关阅读:



专注架构技术研究,一起跨越职业瓶颈!

关注公众号,免费领学习资料



如果您觉得还不错,欢迎关注和转发~     


继续滑动看下一个
架构精进之路
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存